import datetime
import tkinter
import tkinter.scrolledtext
import tkinter.messagebox
import tkinter.filedialog
import tkinter.ttk
import socket
import pymysql
import random
import threading
import time
import struct
import struct
import sys
import string
import docx
import xlrd
import re
import openpyxl
import os
import psutil#用于系统监控，分析和控制系统资源及进程管理
from PIL import ImageGrab

#创建tkinter应用程序主窗口,并自定义关闭窗口的事件处理函数：
root=tkinter.Tk()
root.geometry('360x150+500+300')
root.resizable(False,False)
root.title('课程教学管理系统---学生端')

def closeWindow():
    #结束搜索服务器
    if int_searchServer.get()==1:
        int_searchServer.set(0)
    root.destroy()
root.protocol('VM_DELETE_WINDOW',closeWindow)
#退出程序时执行上面的函数
import atexit
atexit.register(closeWindow)

xuehao=tkinter.StringVar(root)
xuehao.set('2')
xingming=tkinter.StringVar(root)
xingming.set('孙戈')
server_IP=tkinter.StringVar(root)
server_IP.set('192.168.126.1')#默认服务器地址
labelXuHao=tkinter.Label(root,text='学号:',justify=tkinter.RIGHT,width=80)
labelXuHao.place(x=10,y=5,width=80,height=20)#将标签放到窗口上
entryXuehao=tkinter.Entry(root,width=150,textvariable=xuehao)#同时设置关联的变量
entryXuehao.place(x=100,y=5,width=150,height=20)

labelXingming=tkinter.Label(root,text='姓名：',justify=tkinter.RIGHT,width=80)
labelXingming.place(x=10,y=30,width=80,height=20)
entryXingming=tkinter.Entry(root,width=150,textvariable=xingming)
entryXingming.place(x=100,y=30,width=150,height=20)

labelServerIP=tkinter.Label(root,text='服务器IP地址：',justify=tkinter.RIGHT,width=80)
labelServerIP.place(x=10,y=60,width=80,height=20)
entryServerIP=tkinter.Entry(root,width=150,textvariable=server_IP)
entryServerIP.place(x=100,y=60,width=150,height=20)

int_searchServer=tkinter.IntVar(root,value=1)
#每3秒钟接收一次服务器广播信息，修改服务器IP地址
def findServer():
    sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)#创建socket对象
    sock.bind(('',50000))#绑定socket
    try:
        while int_searchServer.get()==1:
            data,addr=sock.recvfrom(1024)#接收信息
            if data.decode()=='ServerIP':
                server_IP.set(addr[0])
            time.sleep(3)
    except:
        pass
thread_findServer=threading.Thread(target=findServer)
thread_findServer.start()

#每10秒钟判断一次服务器是否存活
def isServerAlive():
    serverIP=entryServerIP.get()
    if not re.match('^\d{1,3}\.d{1,3}\.d{1,3}\.d{1,3}$',serverIP):
        tkinter.messagebox.showerror('很抱歉','服务器IP地址不合法')
    sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    try:
        sock.connect((serverIP,11111))
        sock.close()
    except Exception as e:
        tkinter.messagebox.showerror('很抱歉','服务器失去连接，请检查服务器IP地址或联系老师')
    time.sleep(10)
t=threading.Thread(target=isServerAlive)
t.daemon=True
t.start()
    
    #登录按钮事件处理函数
def buttonOkClick():
    #获取学号和姓名
    xuehao=entryXuehao.get()
    xingming=entryXingming.get()
    serverIP=entryServerIP.get()
    print(xuehao)
    if not re.match('^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$',serverIP):
        tkinter.messagebox.showerror(title='很抱歉',
                                        message='服务器IP地址不合法')
        return
    #创建Socket，并连接教师端
    sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    try:
        sock.connect((serverIP,30000))        
    except Exception as e:
        tkinter.messagebox.showerror('很抱歉','现在不是点名时间')
        return
    #向教师端发送学号和姓名，然后等待教师端反馈信息
    print('a')
    sock.sendall((xuehao+','+xingming+','+serverIP).encode())
    data=sock.recv(1024)
    data=data.decode()
    
    #根据教师端的反馈信息进行相应的提示
    if data.lower()=='ok':
        #点名成功
        tkinter.messagebox.showinfo('恭喜',xuehao+','+xingming+'报到点名成功')
        sock.close()
        return
    elif data.lower()=='repeat':
        tkinter.messagebox.showinfo('很抱歉','不允许重复报到')
        sock.close()
        return
    elif data.lower()=='notmatch':
        tkinter.messagebox.showinfo('很抱歉','学号与姓名不匹配')
        sock.close()
        return
    elif data.lower()=='daidianming':
        tkinter.messagebox.showinfo('很抱歉','不允许替别人点名，警告一次')
        sock.close()
        return
    #在学生端应用程序主界面上创建按钮，并设置单击事件处理函数
buttonOk=tkinter.Button(root,text='报到',command=buttonOkClick)
buttonOk.place(x=30,y=90,width=80,height=20)

def buttonZuoyeClick():
    xuehao=entryXuehao.get()#获取学号
    xingming=entryXingming.get()#或取姓名
    serverIP=entryServerIP.get()
    if not re.match('^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$',serverIP):
        tkinter.messagebox.showerror('很抱歉','服务器IP不合法')
        return
    sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    try:
        sock.connect((serverIP,30100))
    except Exception as e:
        tkinter.messagebox.showerror('很抱歉','现在不是交作业时间')
        return
    filename=xuehao+','+xingming+'.png'
    im=ImageGrab.grab()
    im.save(filename)
    im.close()

    BUFSIZE=1024
    FILEINFO_SIZE=struct.calcsize('I128sI')

    #开始发送截图
    fhead=struct.pack('I128sI',len(filename),filename.encode(),os.stat(filename).st_size)
    sock.send(fhead)#发送文件名和大小等信息
    data=sock.recv(1024)#接收服务器反馈
    data=data.decode()
    if data.lower()=='notmatch':
        tkinter.messagebox.showerror('很抱歉','学号和姓名不匹配')
        sock.close()
        return
    fp=open(filename,'rb')
    while True:
        filedata=fp.read(BUFSIZE)
        if not filedata:
            break
        sock.send(filedata)
    fp.close()
    sock.close()
    tkinter.messagebox.showinfo('恭喜','交作业成功')
buttonZuoye=tkinter.Button(root,text='全屏截图交作业',command=buttonZuoyeClick)
buttonZuoye.place(x=120,y=90,width=100,height=20)
#==============================================================================
#提问功能模块（成功）
#==============================================================================
int_windowTiwen=tkinter.IntVar(root,value=0)
string_wenti=tkinter.StringVar(root,value='')
class windowTiwen:
    def __init__(self,root):
        #创建面板容器，用于放置其他控件
        self.top=tkinter.Toplevel(root,width=300,height=180)
        self.top.title('请输入你的问题')
        self.top.attributes('-topmost',1)
        entryMessage=tkinter.scrolledtext.ScrolledText(self.top,wrap=tkinter.WORD)#问题内容
        entryMessage.place(x=10,y=20,width=280,height=100)

        def buttonTiwenClick():
            #回复，解答
            wenti=entryMessage.get(0.0,tkinter.END)
            wenti=wenti.strip()
            string_wenti.set(wenti)
            self.top.destroy()
        buttonTiwen=tkinter.Button(self.top,text='确定提问',command=buttonTiwenClick)
        buttonTiwen.place(x=80,y=130,width=80,height=20)
def buttonTiwenClick():
    if int_windowTiwen.get()==1:
        return
    xuehao=entryXuehao.get()#获取用户名
    xingming=entryXingming.get()#获取密码
    serverIP=entryServerIP.get()
    if not re.match('^(\d){1,3}\.(\d){1,3}\.(\d){1,3}\.(\d){1,3}$', serverIP):
        tkinter.messagebox.showerror('很抱歉', '服务器IP地址不合法')
        return
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    try:
        sock.connect((serverIP, 20000))#++++++++++++++++++++++++++++++++++++
    except Exception as e:
        tkinter.messagebox.showerror('很抱歉', '现在不允许提问！')
        return
    int_windowTiwen.set(1)
    w = windowTiwen(root)
    buttonTiwen.wait_window(w.top)
    int_windowTiwen.set(0)
    if string_wenti.get()!='':
        #如果确实有问题，则向服务器端发送
        message = xuehao+','+xingming+':'+string_wenti.get()
        sock.sendall(message.encode())
        data = sock.recv(1024)
        data = data.decode()
        data = data.strip()
        if data.lower() == 'notmatch':
            tkinter.messagebox.showerror('很抱歉', '学号与姓名不匹配')
            sock.close()
            return
        elif data.lower() == 'wait':
            tkinter.messagebox.showinfo('稍等', '该问题稍后我会统一进行讲解！')
            sock.close()
            return
        else:
            tkinter.messagebox.showinfo('老师回复', data)
            sock.close()
            return
buttonTiwen = tkinter.Button(root, text='提问', command=buttonTiwenClick)
buttonTiwen.place(x=230, y=90, width=40, height=20)
#=======================================================
#自测模块(成功)
#=======================================================
int_windowZice = tkinter.IntVar(root, value=0)
class windowZice:
    def __init__(self, root, conn, xuehaoxingming):
        #创建面板容器，用于放置其他控件
        self.top = tkinter.Toplevel(root, width=300, height=180)
        self.top.title('学生自测---'+xuehaoxingming)
        self.top.attributes('-topmost', 1)
        self.top.resizable(False, False) #不允许最大化
        def closeWindow():
            if int_windowZice.get() == 1:
                int_windowZice.set(0)
                conn.sendall('xxxx'.encode())
                conn.close()
            self.top.destroy()
        self.top.protocol('WM_DELETE_WINDOW', closeWindow)
        #从服务器接收课程名称清单，并用组合框显示
        data = conn.recv(1024)
        data = data.decode()
        kechengQingdan = data.split(',')
        labelKechengmingcheng = tkinter.Label(self.top, text='请选择课程名称：')
        labelKechengmingcheng.place(x=10, y=10, height=20, width=100)
        comboboxKechengmingcheng = tkinter.ttk.Combobox(self.top, values=kechengQingdan)
        comboboxKechengmingcheng.place(x=120, y=10, height=20, width=130)
        #每次改变课程名称时，把self.currentID重新设置为0
        def comboxboxKechengmingChanged(event):
            self.currentID = 0
        comboboxKechengmingcheng.bind('<<ComboboxSelected>>', comboxboxKechengmingChanged)

        #课程名称
        string_Kecheng = tkinter.StringVar(self.top, value='')
        labelKecheng = tkinter.Label(self.top,text='', textvariable=string_Kecheng)
        labelKecheng.place(x=10, y= 40, height=20, width=100)
        #章节
        string_Zhangjie = tkinter.StringVar(self.top, value='')
        labelZhangjie = tkinter.Label(self.top, text='', textvariable=string_Zhangjie)
        labelZhangjie.place(x=130, y=40, height=20, width=80)

        #选择题号组合框
        tihao = [i for i in range(1,800)]
        comboTihao = tkinter.ttk.Combobox(self.top, values=tihao, width=50)
        comboTihao.place(x=220, y=40, height=20, width=50)
        def comboTihaoChanged(event):
            self.currentID = int(comboTihao.get())-1
            buttonNextClick()
        comboTihao.bind('<<ComboboxSelected>>', comboTihaoChanged)
        
        entryMessage = tkinter.scrolledtext.ScrolledText(self.top, wrap=tkinter.WORD)#问题内容
        entryMessage.place(x=10, y=70, width=280, height=70)
        self.currentID = 0
        def buttonPreClick():#上一题
            kechengmingchengSelected = comboboxKechengmingcheng.get()
            if not kechengmingchengSelected:
                tkinter.messagebox.showerror('很抱歉', '请选择课程名称')
                return
            message = kechengmingchengSelected+'xx'+str(self.currentID)+'xxpre'
            conn.sendall(message.encode())
            data = conn.recv(1024)
            data = data.decode()
            print(data)
            if data == 'no':
                tkinter.messagebox.showerror('很抱歉', '没有上一题了！')
                return
            kechengmingcheng, zhangjie, timu, self.daan, self.currentID = data.split('xx')
            entryMessage.delete(0.0, tkinter.END)#删除原来的题目内容
            entryMessage.insert(tkinter.INSERT, timu)#显示新题目内容
            string_Kecheng.set(kechengmingcheng)
            string_Zhangjie.set(zhangjie)
            #将题号选择组合框设置为当前题号
            comboTihao.set(self.currentID)
            
            
        buttonPre = tkinter.Button(self.top, text='上一题', command=buttonPreClick)
        buttonPre.place(x=40, y=150, width=60, height=20)
        def buttonNextClick():#下一题
            kechengmingchengSelected = comboboxKechengmingcheng.get()
            if not kechengmingchengSelected:
                tkinter.messagebox.showerror('很抱歉', '请选择课程名称')
                return
            message = kechengmingchengSelected+'xx'+str(self.currentID)+'xxnext'
            conn.sendall(message.encode())
            data = conn.recv(1024)
            data = data.decode()
            if data == 'no':
                tkinter.messagebox.showerror('很抱歉', '没有下一题了！')
                return
            kechengmingcheng, zhangjie, timu, self.daan, self.currentID = data.split('xx')
            print('题目是'+timu)
            entryMessage.delete(0.0, tkinter.END)#删除原来的题目内容
            entryMessage.insert(tkinter.INSERT, timu)#显示新题目内容
            string_Kecheng.set(kechengmingcheng)
            string_Zhangjie.set(zhangjie)
            #将题号选择组合框设置为当前题号
            comboTihao.set(self.currentID)
        buttonNext = tkinter.Button(self.top, text='下一题', command=buttonNextClick)
        buttonNext.place(x=110, y=150, width=60, height=20)
        def buttonDaanClick():#查看答案
            tkinter.messagebox.showinfo('本题答案', self.daan)
        buttonDaan = tkinter.Button(self.top, text='查看答案', command=buttonDaanClick)
        buttonDaan.place(x=180, y=150, width=60, height=20)
        #默认打开第一题
        buttonNextClick()
def buttonZiceClick():
    xuehao = entryXuehao.get()
    xingming = entryXingming.get()
    serverIP = entryServerIP.get()
    if not re.match('(\d){1,3}\.(\d){1,3}\.(\d){1,3}\.(\d){1,3}', serverIP):
        tkinter.messagebox.showerror('很抱歉', '服务器IP地址不合法')
        return
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    try:
        sock.connect((serverIP, 10000))
    except Exception as e:
        tkinter.messagebox.showerror('很抱歉', '现在不是自测时间')
        return
    #向服务端发送学号和姓名
    sock.sendall((xuehao+','+xingming).encode())
    #打开自测窗口
    int_windowZice.set(1)
    w = windowZice(root, sock, xuehao+','+xingming)
    buttonZice.wait_window(w.top)
    int_windowZice.set(0)
        
buttonZice = tkinter.Button(root, text='自我测试', command=buttonZiceClick) 
buttonZice.place(x=30, y=120, width=80, height=20)

#上传文件交作业，服务端应监听10500端口
def buttonShangchuanWenjianJiaozuoyeClick():
    xuehao = entryXuehao.get()  #获取用户名
    xingming = entryXingming.get()    #获取密码
    serverIP = entryServerIP.get()
    if not re.match('^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$', serverIP):
        tkinter.messagebox.showerror('很抱歉', '服务器IP地址不合法')
        return
    
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    try:
        sock.connect((serverIP, 10500))
    except Exception as e:
        tkinter.messagebox.showerror('很抱歉', '现在不是交作业时间')
        return
    #检查学生填写的学号和姓名是否匹配
    filename = tkinter.filedialog.askopenfilename(title='请选择要上传的Python程序或压缩文件', filetypes=[('Python Files','*.py'), ('Python GUI Files','*.pyw'),('Rar files', '*.rar'), ('Zip files', '*.zip')])
    if filename:
        BUFSIZE = 1024
        FILEINFO_SIZE=struct.calcsize('I128sI')
        temp = xuehao+'_'+xingming+'_1_'+filename
        fhead=struct.pack('I128sI',len(temp),temp.encode(),os.stat(filename).st_size)
        sock.send(fhead)#发送文件名和大小等信息
        data = sock.recv(1024)#接收服务器反馈
        data = data.decode()
        if data.lower() == 'notmatch':
            tkinter.messagebox.showerror('很抱歉', '学号与姓名不匹配')
            sock.close()
            return
            
        fp = open(filename,'rb')
        while True:
            filedata = fp.read(BUFSIZE)
            if not filedata:
                break
            sock.send(filedata)
        fp.close()
        sock.close()
        tkinter.messagebox.showinfo('恭喜', '交作业成功')
        
buttonShangchuanWenjianJiaozuoye = tkinter.Button(root, text='上传文件交作业', command=buttonShangchuanWenjianJiaozuoyeClick) 
buttonShangchuanWenjianJiaozuoye.place(x=120, y=120, width=100, height=20)

#在线考试模块
int_windowKaoshi = tkinter.IntVar(root, value=0)
class windowKaoshi:
    def __init__(self, root, conn, xuehaoxingming):
        #创建面板容器，用于放置其他控件
        self.top = tkinter.Toplevel(root, width=300, height=220)
        self.top.title('在线考试---'+xuehaoxingming)
        self.top.attributes('-topmost', 1)
        self.top.resizable(False, False) #不允许最大化
        
        #关闭窗口事件
        def closeWindow():
            if int_windowZice.get() == 1:
                int_windowZice.set(0)
                conn.sendall('xxxx'.encode())
                conn.close()
                #结束禁用word，wps，记事本进程的线程
                self.jinyong = False
            self.top.destroy()
        self.top.protocol('WM_DELETE_WINDOW', closeWindow)

        #禁用word,wps,记事本等文本编辑器进程
        self.jinyong = True
        import psutil
        import os
        import os.path
        import time
        import threading
        def funcJinyong():
            while self.jinyong:
                for pid in psutil.pids():
                    try:
                            p = psutil.Process(pid)
                            if os.path.basename(p.exe()).lower() in ('notepad.exe', 'winword.exe', 'wps.exe', 'wordpad.exe', 'iexplore.exe', 'chrome.exe', 'qqbrowser.exe', '360chrome.exe', '360se.exe', 'sogouexplorer.exe', 'firefox.exe', 'opera.exe', 'maxthon.exe', 'netscape.exe', 'baidubrowser.exe', '2345Explorer.exe'):
                                    p.kill()
                    except:
                            pass
                ctypes.windll.user32.OpenClipboard(None)
                ctypes.windll.user32.EmptyClipboard()
                ctypes.windll.user32.CloseClipboard()
                time.sleep(1)
        t_jinyong = threading.Thread(target=funcJinyong)
        t_jinyong.start()
        
        #从服务器接收课程名称清单，并用组合框显示
        data = conn.recv(1024)
        data = data.decode()
        kechengQingdan = data.split(',')
        print(kechengQingdan)
        labelKechengmingcheng = tkinter.Label(self.top, text='请选择课程名称：')
        labelKechengmingcheng.place(x=10, y=10, height=20, width=100)
        comboboxKechengmingcheng = tkinter.ttk.Combobox(self.top, values=kechengQingdan)
        comboboxKechengmingcheng.place(x=120, y=10, height=20, width=130)
        #每次改变课程名称时，把self.currentID重新设置为0
        def comboxboxKechengmingChanged(event):
            self.currentID = 0
        comboboxKechengmingcheng.bind('<<ComboboxSelected>>', comboxboxKechengmingChanged)

        #课程名称
        string_Kecheng = tkinter.StringVar(self.top, value='')
        labelKecheng = tkinter.Label(self.top,text='', textvariable=string_Kecheng)
        labelKecheng.place(x=10, y= 30, height=20, width=200)

        #已做多少题
        string_total = tkinter.StringVar(self.top, value='')
        labelTotal = tkinter.Label(self.top, text='', textvariable=string_total)
        labelTotal.place(x=120, y=40, height=20, width=100)
        
        entryMessage = tkinter.scrolledtext.ScrolledText(self.top, wrap=tkinter.WORD)#问题内容
        entryMessage.place(x=10, y=70, width=280, height=70)
        
        def buttonNextClick():#下一题
            kechengmingchengSelected = comboboxKechengmingcheng.get()
            print('你选择的是'+kechengmingchengSelected)
            if not kechengmingchengSelected:
                tkinter.messagebox.showerror('很抱歉', '请选择课程名称')
                return

            
            #必须做这个题
            if entryMessage.get(0.0).strip()!='' and entryDaan.get().strip()=='':
                tkinter.messagebox.showinfo('很抱歉', '必须做这个题')
                return

            #检查答案长度，禁止向服务器发送太长的内容
            if len(entryDaan.get()) >= 200:
                tkinter.messagebox.showerror('很抱歉', '答案太长')
                return

            #提交答案，同时获取下一题
            message = kechengmingchengSelected+'xx'+str(self.currentID)+'xx'+entryDaan.get()+'xxnext'
            print(message)
            conn.sendall(message.encode())
            data = conn.recv(1024)
            data = data.decode()
            print(data)
            print('===============================================')
            if data.startswith('no'):
                print('我进来了')
                fenshu = data.split('no')[1]
                tkinter.messagebox.showinfo(title='恭喜', message='你已经完成考试，得分：'+fenshu)
                buttonNext['state'] = 'disabled'#禁用按钮
                #结束禁用word，wps，记事本进程的线程
                self.jinyong = False
                return
            #print(data)
            kechengmingcheng, zhangjie, timu, self.currentID, total = data.split('xx')
            entryMessage.delete(0.0, tkinter.END)#删除原来的题目内容
            entryMessage.insert(tkinter.INSERT, timu)#显示新题目内容
            string_Kecheng.set(kechengmingcheng)
            entryDaan.delete(0, tkinter.END)   #删除上一题学生输入的答案
            string_total.set('已答 '+str(total)+' 道题')
        buttonNext = tkinter.Button(self.top, text='下一题', command=buttonNextClick)
        buttonNext.place(x=10, y=150, width=60, height=20)

        #填写答案的文本框
        entryDaan = tkinter.Entry(self.top,)
        entryDaan.place(x=10,y=180, width=270, height=20)
        
        #默认打开第一题
        buttonNextClick()

def buttonKaoshiClick():
    xuehao = entryXuehao.get()
    xingming = entryXingming.get()
    serverIP = entryServerIP.get()
    if not re.match('(\d){1,3}\.(\d){1,3}\.(\d){1,3}\.(\d){1,3}', serverIP):
        tkinter.messagebox.showerror('很抱歉', '服务器IP地址不合法')
        return
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    try:
        sock.connect((serverIP, 18002))
    except Exception as e:
        tkinter.messagebox.showerror('很抱歉', '现在不是考试时间')
        return
    #向服务端发送学号和姓名
    sock.sendall((xuehao+','+xingming).encode())
    #等待服务器回应，检查学号姓名是否匹配
    data = sock.recv(1024)
    data = data.decode()
    data = data.strip()
    if data.lower() == 'notmatch':
        tkinter.messagebox.showerror('很抱歉', '学号与姓名不匹配')
        sock.close()
        return
    
    #打开自测窗口
    int_windowKaoshi.set(1)
    w = windowKaoshi(root, sock, xuehao+','+xingming)
    buttonZice.wait_window(w.top)
    int_windowKaoshi.set(0)
buttonKaoshi = tkinter.Button(root, text='考试', command=buttonKaoshiClick)
buttonKaoshi.place(x=230, y=120, width=40, height=20)


root.mainloop()          #启动消息循环


    
